package pl.edu.pb.wi.cookbook.service;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import pl.edu.pb.wi.cookbook.entity.Cookbook;
import pl.edu.pb.wi.cookbook.entity.CookbookRecipe;
import pl.edu.pb.wi.cookbook.entity.Recipe;
import pl.edu.pb.wi.cookbook.entity.User;
import pl.edu.pb.wi.cookbook.util.FullTextSearchUtil;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.Query;

@Service
public class CookbookService implements IFullTextSearch<Cookbook> {
    private Objectify oby;

    @Autowired
    public CookbookService(ObjectifyFactory objectifyFactory) {
        this.oby = objectifyFactory.begin();
    }

    public Cookbook createCookbook(Long authorId, String name,
            String description, boolean isPrivate) {
        Cookbook newBook = new Cookbook();
        newBook.setAuthor(new Key<User>(User.class, authorId));
        newBook.setName(name);
        newBook.setDescription(description);
        newBook.setPrivate(isPrivate);

        newBook.updateFts();

        oby.put(newBook);

        return newBook;
    }

    public Cookbook getFirstCookbook() {
        return oby.query(Cookbook.class).get();
    }

    public Cookbook createCookbook(User author, String name,
            String description, boolean isPrivate) {
        Cookbook newBook = new Cookbook();
        newBook.setAuthor(new Key<User>(User.class, author.getId()));
        newBook.setName(name);
        newBook.setDescription(description);
        newBook.setPrivate(isPrivate);

        newBook.updateFts();

        oby.put(newBook);

        return newBook;
    }

    public CookbookRecipe addRecipeToCookbook(Cookbook book, Recipe recipe) {
        Key<Cookbook> bookKey = new Key<Cookbook>(Cookbook.class, book.getId());
        Key<Recipe> recipeKey = new Key<Recipe>(Recipe.class, recipe.getId());

        CookbookRecipe cookRecipe = oby.query(CookbookRecipe.class)
                .filter("cookbook", bookKey).filter("recipe", recipeKey).get();

        /*
         * Nie wiem co zrobić. Rzucać wyjątek jak wpis już istnieje, czy to olać
         * i zachowywać się jakby to był nowy wpis.
         */
        if (cookRecipe == null) {
            cookRecipe = new CookbookRecipe();
            cookRecipe.setCookbook(bookKey);
            cookRecipe.setRecipe(recipeKey);
            oby.put(cookRecipe);
        }

        return cookRecipe;
    }

    public CookbookRecipe addRecipeToCookbook(Key<Cookbook> book,
            Key<Recipe> recipe) {
        CookbookRecipe cookRecipe = oby.query(CookbookRecipe.class)
                .filter("cookbook", book).filter("recipe", recipe).get();

        /*
         * Nie wiem co zrobić. Rzucać wyjątek jak wpis już istnieje, czy to olać
         * i zachowywać się jakby to był nowy wpis.
         */
        if (cookRecipe == null) {
            cookRecipe = new CookbookRecipe();
            cookRecipe.setCookbook(book);
            cookRecipe.setRecipe(recipe);
            oby.put(cookRecipe);
        }

        return cookRecipe;
    }

    /**
     * @param book
     * @param recipe
     * @return null, gdy nie istnieje dany przepis w danej książce.
     */
    public CookbookRecipe removeRecipeFromCookbook(Cookbook book, Recipe recipe) {
        Key<Cookbook> bookKey = new Key<Cookbook>(Cookbook.class, book.getId());
        Key<Recipe> recipeKey = new Key<Recipe>(Recipe.class, recipe.getId());

        CookbookRecipe cookRecipe = oby.query(CookbookRecipe.class)
                .filter("cookbook", bookKey).filter("recipe", recipeKey).get();

        if (cookRecipe != null) {
            oby.delete(cookRecipe);
        }

        return cookRecipe;
    }

    /**
     * @param book
     * @param recipe
     * @return null, gdy nie istnieje dany przepis w danej książce.
     */
    public CookbookRecipe removeRecipeFromCookbook(Key<Cookbook> book,
            Key<Recipe> recipe) {
        CookbookRecipe cookRecipe = oby.query(CookbookRecipe.class)
                .filter("cookbook", book).filter("recipe", recipe).get();

        if (cookRecipe != null) {
            oby.delete(cookRecipe);
        }

        return cookRecipe;
    }

    public void removeCookbook(Cookbook book) {
        List<Key<CookbookRecipe>> recipesConnectedWithCookbook = oby
                .query(CookbookRecipe.class)
                .filter("cookbook",
                        new Key<Cookbook>(Cookbook.class, book.getId()))
                .listKeys();

        if (recipesConnectedWithCookbook != null)
            oby.delete(recipesConnectedWithCookbook);
        oby.delete(book);
    }

    public void removeCookbook(Key<Cookbook> book) {
        List<Key<CookbookRecipe>> recipesConnectedWithCookbook = oby
                .query(CookbookRecipe.class).filter("cookbook", book)
                .listKeys();

        oby.delete(recipesConnectedWithCookbook);
        oby.delete(book);
    }

    public Key<Cookbook> updateCookbook(Cookbook book) {
        book.updateFts();

        return oby.put(book);
    }

    public List<Cookbook> getUsersCookbooks(User us) {
        return oby.query(Cookbook.class)
                .filter("author", new Key<User>(User.class, us.getId())).list();
    }

    public Integer getCookbookRecipeCount(Long id) {
        List<CookbookRecipe> recList = oby.query(CookbookRecipe.class)
                .filter("cookbook", new Key<Cookbook>(Cookbook.class, id))
                .list();

        return recList != null ? recList.size() : null;
    }

    public Cookbook getCookBook(Long id) {
        return oby.get(new Key<Cookbook>(Cookbook.class, id));
    }

    public List<Recipe> getRecipesFromCookbook(Cookbook book) {
        List<CookbookRecipe> bookRecipeKeys = oby
                .query(CookbookRecipe.class)
                .filter("cookbook",
                        new Key<Cookbook>(Cookbook.class, book.getId())).list();

        List<Key<Recipe>> recipeKeys = new ArrayList<Key<Recipe>>();
        for (CookbookRecipe cr : bookRecipeKeys)
            recipeKeys.add(cr.getRecipe());

        Map<Key<Recipe>, Recipe> recipeMap = oby.get(recipeKeys);

        return new ArrayList<Recipe>(recipeMap.values());
    }

    public boolean isCookbookActive(Cookbook current, Cookbook menuCook) {
        if (current != null && menuCook != null) {

            return current.getId().equals(menuCook.getId());
        } else
            return false;
    }

    public boolean isUserOwner(User user, Recipe rec) {
        if (user != null && rec != null)
            return rec.getAuthor().getId() == user.getId() ? true : false;
        return false;
    }

    public List<Recipe> getRecipesFromCookbook(Key<Cookbook> book) {
        List<CookbookRecipe> bookRecipeKeys = oby.query(CookbookRecipe.class)
                .filter("cookbook", book).list();

        List<Key<Recipe>> recipeKeys = new ArrayList<Key<Recipe>>();
        for (CookbookRecipe cr : bookRecipeKeys)
            recipeKeys.add(cr.getRecipe());

        Map<Key<Recipe>, Recipe> recipeMap = oby.get(recipeKeys);

        return new ArrayList<Recipe>(recipeMap.values());
    }

    public boolean isNameUniqueInUserProfile(User user, String name) {
        return oby.query(Cookbook.class)
                .filter("author", new Key<User>(User.class, user.getId()))
                .filter("name", name).get() == null ? Boolean.TRUE
                : Boolean.FALSE;
    }

    public List<Cookbook> getUsersCookbooksWithoutRecipe(User user,
            Recipe recipe) {
        List<Cookbook> userBooks = getUsersCookbooks(user);

        List<Cookbook> booksWithoutRecipe = new LinkedList<Cookbook>();
        CookbookRecipe bookWithRecipe;
        for (Cookbook book : userBooks) {
            bookWithRecipe = oby
                    .query(CookbookRecipe.class)
                    .filter("cookbook",
                            new Key<Cookbook>(Cookbook.class, book.getId()))
                    .filter("recipe",
                            new Key<Recipe>(Recipe.class, recipe.getId()))
                    .get();

            if (bookWithRecipe == null)
                booksWithoutRecipe.add(book);
        }

        return booksWithoutRecipe;
    }

    public boolean isBookWithoutRecipe(Cookbook book, Recipe recipe) {
        CookbookRecipe bookWithRecipe = oby
                .query(CookbookRecipe.class)
                .filter("cookbook",
                        new Key<Cookbook>(Cookbook.class, book.getId()))
                .filter("recipe", new Key<Recipe>(Recipe.class, recipe.getId()))
                .get();

        return bookWithRecipe == null ? true : false;
    }

    @Override
    public List<Cookbook> search(String queryString) {
        Query<Cookbook> query = oby.query(Cookbook.class);

        Set<String> queryTokens = FullTextSearchUtil
                .getTokensForIndexingOrQuery(queryString,
                        FullTextSearchUtil.MAXIMUM_NUMBER_OF_WORDS_TO_SEARCH);
        List<String> queryTokensList = new ArrayList<String>(queryTokens);

        for (String token : queryTokensList) {
            query.filter("fts", token);
        }

        List<Cookbook> result = new ArrayList<Cookbook>();
        for (Cookbook book : query) {
            result.add(book);
        }

        return result;
    }
}
